#!/usr/bin/env python3

# Copyright (c) 2020 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder.  You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Copyright (c) 2017-2018 Metempsy Technology Consulting
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import sys
from argparse import (
    ArgumentDefaultsHelpFormatter,
    ArgumentParser,
)
from distutils import spawn
from glob import glob
from platform import machine
from subprocess import call


def run_cmd(explanation, working_dir, cmd, stdout=None):
    print(f"Running phase '{explanation}'")
    sys.stdout.flush()

    # some of the commands need $PWD to be properly set
    env = os.environ.copy()
    env["PWD"] = working_dir

    return_code = call(cmd, cwd=working_dir, stdout=stdout, env=env)

    if return_code == 0:
        return

    print(
        "Error running phase %s. Returncode: %d" % (explanation, return_code)
    )
    sys.exit(1)


def linux_clone():
    kernel_vexpress_gem5_dir = os.path.join(
        args.dest_dir, "linux-kernel-vexpress_gem5"
    )

    run_cmd(
        "clone linux kernel for VExpress_GEM5_V1 platform",
        args.dest_dir,
        [
            "git",
            "clone",
            "https://gem5.googlesource.com/arm/linux",
            kernel_vexpress_gem5_dir,
        ],
    )


def linux64():
    kernel_vexpress_gem5_dir = os.path.join(
        args.dest_dir, "linux-kernel-vexpress_gem5"
    )

    linux_bin = os.path.join(binaries_dir, "vmlinux.vexpress_gem5_v1_64")

    with open(revisions_dir + "/linux", "w+") as rev_file:
        run_cmd(
            "write revision of linux-kernel-vexpress_gem5 repo",
            kernel_vexpress_gem5_dir,
            ["git", "rev-parse", "--short", "HEAD"],
            rev_file,
        )

    run_cmd(
        "configure kernel for arm64",
        kernel_vexpress_gem5_dir,
        [
            "make",
            "ARCH=arm64",
            "CROSS_COMPILE=aarch64-linux-gnu-",
            "gem5_defconfig",
            make_jobs_str,
        ],
    )
    run_cmd(
        "compile kernel for arm64",
        kernel_vexpress_gem5_dir,
        [
            "make",
            "ARCH=arm64",
            "CROSS_COMPILE=aarch64-linux-gnu-",
            make_jobs_str,
        ],
    )
    run_cmd(
        "copy arm64 vmlinux",
        kernel_vexpress_gem5_dir,
        ["cp", "vmlinux", linux_bin],
    )
    run_cmd(
        "cleanup arm64 kernel compilation",
        kernel_vexpress_gem5_dir,
        ["make", "distclean"],
    )


def linux32():
    kernel_vexpress_gem5_dir = os.path.join(
        args.dest_dir, "linux-kernel-vexpress_gem5"
    )

    linux_bin = os.path.join(binaries_dir, "vmlinux.vexpress_gem5_v1")

    run_cmd(
        "configure kernel for arm",
        kernel_vexpress_gem5_dir,
        [
            "make",
            "ARCH=arm",
            "CROSS_COMPILE=arm-linux-gnueabihf-",
            "gem5_defconfig",
        ],
    )
    run_cmd(
        "compile kernel for arm",
        kernel_vexpress_gem5_dir,
        [
            "make",
            "ARCH=arm",
            "CROSS_COMPILE=arm-linux-gnueabihf-",
            make_jobs_str,
        ],
    )
    run_cmd(
        "copy arm vmlinux",
        kernel_vexpress_gem5_dir,
        ["cp", "vmlinux", linux_bin],
    )


def linux():
    """
    Checkout and build linux kernel for VExpress_GEM5_V1 (arm and arm64)
    """
    linux_clone()
    linux64()
    linux32()


def dtbs():
    """
    Build DTBs for VExpress_GEM5_V1
    """
    dt_dir = gem5_dir + "/system/arm/dt"
    run_cmd(
        "compile DTBs for VExpress_GEM5_V1 platform",
        dt_dir,
        ["make", make_jobs_str],
    )
    run_cmd(
        "copy DTBs", dt_dir, ["cp"] + glob(dt_dir + "/*dtb") + [binaries_dir]
    )


def bootloaders():
    """
    Build bootloaders arm64/arm
    """

    bootloader_arm64_dir = gem5_dir + "/system/arm/bootloader/arm64"
    run_cmd("compile arm64 bootloader", bootloader_arm64_dir, ["make"])
    run_cmd(
        "copy arm64 bootloader",
        bootloader_arm64_dir,
        ["cp", "boot.arm64", "boot_emm.arm64", "boot_v2.arm64", binaries_dir],
    )

    bootloader_arm_dir = gem5_dir + "/system/arm/bootloader/arm"
    run_cmd("compile arm bootloader", bootloader_arm_dir, ["make"])
    run_cmd(
        "copy arm bootloaders",
        bootloader_arm_dir,
        ["cp", "boot.arm", "boot_emm.arm", binaries_dir],
    )


def m5():
    """
    Build m5 binaries
    """
    m5_dir = gem5_dir + "/util/m5"
    run_cmd("compile arm64 m5", m5_dir, ["make", "-f", "Makefile.aarch64"])
    run_cmd(
        "copy arm64 m5", m5_dir, ["cp", "m5", binaries_dir + "/m5.aarch64"]
    )
    run_cmd(
        "clean arm64 m5", m5_dir, ["make", "clean", "-f", "Makefile.aarch64"]
    )
    run_cmd("compile arm m5", m5_dir, ["make", "-f", "Makefile.arm"])
    run_cmd("copy arm m5", m5_dir, ["cp", "m5", binaries_dir + "/m5.aarch32"])


def xen():
    """
    Build Xen for aarch64
    """
    xen_dir = os.path.join(args.dest_dir, "xen")
    bootwrapper_dir = os.path.join(args.dest_dir, "bootwrapper")
    linux_cmdline = "console=hvc0 root=/dev/vda rw mem=1G"
    xen_cmdline = (
        "dtuart=/uart@1c090000 console=dtuart no-bootscrub "
        + "dom0_mem=1G loglvl=all guest_loglvl=all"
    )

    run_cmd(
        "clone Xen",
        args.dest_dir,
        ["git", "clone", "git://xenbits.xen.org/xen.git", xen_dir],
    )

    run_cmd(
        "clone boot-wrapper-aarch64",
        args.dest_dir,
        [
            "git",
            "clone",
            "git://git.kernel.org/pub/"
            + "scm/linux/kernel/git/mark/boot-wrapper-aarch64.git",
            bootwrapper_dir,
        ],
    )

    # Need to compile arm64 Linux
    linux_dir = os.path.join(args.dest_dir, "linux-kernel-vexpress_gem5")
    linux_bin = os.path.join(linux_dir, "arch", "arm64", "boot", "Image")
    if not os.path.exists(linux_bin):
        linux_clone()
        linux64()

    # Need to compile DTBs
    dtb_bin = os.path.join(binaries_dir, "armv8_gem5_v2_1cpu.dtb")
    if not os.path.exists(dtb_bin):
        dtbs()

    # Building Xen
    run_cmd(
        "building xen for aarch64",
        xen_dir,
        [
            "make",
            "dist-xen",
            "XEN_TARGET_ARCH=arm64",
            "CROSS_COMPILE=aarch64-linux-gnu-",
            "CONFIG_EARLY_PRINTK=vexpress",
            make_jobs_str,
        ],
    )

    # Building boot-wrapper-aarch64
    run_cmd(
        "autoreconf boot-wrapper-aarch64",
        bootwrapper_dir,
        ["autoreconf", "-i"],
    )
    run_cmd(
        "configure boot-wrapper-aarch64",
        bootwrapper_dir,
        [
            "./configure",
            "--host=aarch64-linux-gnu",
            f"--with-kernel-dir={linux_dir}",
            f"--with-dtb={dtb_bin}",
            f"--with-cmdline='{linux_cmdline}'",
            f"--with-xen-cmdline='{xen_cmdline}'",
            f"--with-xen={os.path.join(xen_dir, 'xen', 'xen')}",
            "--enable-psci",
            "--enable-gicv3",
        ],
    )
    run_cmd("build boot-wrapper-aarch64", bootwrapper_dir, ["make"])

    # Copying the final binary
    run_cmd(
        "copy xen binary",
        bootwrapper_dir,
        ["cp", "xen-system.axf", binaries_dir],
    )

    with open(os.path.join(revisions_dir, "xen"), "w+") as rev_file:
        run_cmd(
            "write revision of xen repo",
            xen_dir,
            ["git", "rev-parse", "--short", "HEAD"],
            rev_file,
        )


script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
gem5_dir = os.path.dirname(script_dir)

all_binaries = {
    "linux": linux,
    "dtbs": dtbs,
    "bootloaders": bootloaders,
    "m5": m5,
    "xen": xen,
}

parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)

parser.add_argument(
    "--gem5-dir",
    default=gem5_dir,
    metavar="GEM5_DIR",
    help="gem5 root directory to be used for bootloader and "
    "VExpress_GEM5_V1 DTB sources. The default value is the gem5 root "
    "directory of the executed script",
)
parser.add_argument(
    "--dest-dir",
    default="/tmp",
    metavar="DEST_DIR",
    help="Directory to use for checking out the different kernel "
    "repositories. Generated files will be copied to "
    "DEST_DIR/binaries (which must not exist)",
)
parser.add_argument(
    "-j",
    "--make-jobs",
    type=int,
    default=1,
    metavar="MAKE_JOBS",
    help="Number of jobs to use with the 'make' commands.",
)
parser.add_argument(
    "-b",
    "--fs-binaries",
    action="append",
    choices=list(all_binaries.keys()),
    default=[],
    help="List of FS files to be generated. Defaulting to all",
)

args = parser.parse_args()

if not os.path.isdir(args.dest_dir):
    print(f"Error: {args.dest_dir} is not a directory.")
    sys.exit(1)

if not os.path.isdir(args.gem5_dir):
    print(f"Error: {args.gem5_dir} is not a directory.")
    sys.exit(1)

if machine() != "x86_64":
    print("Error: This script should run in a x86_64 machine")
    sys.exit(1)

binaries_dir = args.dest_dir + "/binaries"

if os.path.exists(binaries_dir):
    print(f"Error: {binaries_dir} already exists.")
    sys.exit(1)

revisions_dir = args.dest_dir + "/revisions"

if os.path.exists(revisions_dir):
    print(f"Error: {revisions_dir} already exists.")
    sys.exit(1)

os.mkdir(binaries_dir)
os.mkdir(revisions_dir)

make_jobs_str = "-j" + str(args.make_jobs)

rev_file = open(revisions_dir + "/gem5", "w+")
run_cmd(
    "write revision of gem5 repo",
    gem5_dir,
    ["git", "rev-parse", "--short", "HEAD"],
    rev_file,
)
rev_file.close()

binaries = args.fs_binaries if args.fs_binaries else list(all_binaries.keys())
for fs_binary in binaries:
    all_binaries[fs_binary]()

print(f"Done! All the generated files can be found in {binaries_dir}")

sys.exit(0)
